package com.sunday.common.mq.rabbit.study.base.e18_QuorumQueues;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.sunday.common.mq.rabbit.study.base.ConnectionUtil;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;

import java.util.Collections;

/**
 * 总结 : 资源换性能
 * https://bbs.huaweicloud.com/blogs/364672
 * 队列 CMQ(Cloud Message Queue)
 * <p>
 * RabbitMQ集群架构模式
 * 用RabbitMQ集群架构模式来解决消息队列高可用。RabbitMQ集群包含四种架构模式：(1)主备模式 (2)镜像模式 (3)远程模式 (4)多活模式。
 * 主备模式：实现RabbitMQ的高可用集群，即一主一备，一般用在并发和数据量不高的情况下。即两台MQ服务器，一台在工作，一台在闲置，当一台服务器宕机了另一台服务器接上。他有个缺点就是不均衡，资源浪费严重。
 * 镜像模式：能保证100%数据不丢失，但开销很大，每一个RabbitMQ实例都会拥有一个QUEUE队列，一个RabbitMQ实例对QUEUE队列数据的修改都会同步到所有的RabbitMQ实例的队列中。RabbitMQ实例包括队列和交换机等信息。
 * 远程模式：远程模式就是可以把消息进行不同数据中心的复制工作，跨地域的让两个mq集群互联。北京、上海、成都三个集群同步，任何一个坏了都可以顶上。但是配置很复杂。
 * 多活模式：解决了远程模式的配置复杂，实现异地数据复制的主流模式，依赖插件，可以实现持续的可靠的AMQP数据通信。
 * <p>
 * https://www.rabbitmq.com/quorum-queues.html#feature-comparison
 * <p>
 * 仲裁队列
 * 仲裁队列是 RabbitMQ 实现持久、 基于Raft共识算法的复制FIFO队列。 它从 RabbitMQ 3.8.0 开始可用。
 * 仲裁队列和流取代了持久镜像队列，即原始队列 复制的队列类型，现已弃用并计划删除。
 * 仲裁队列针对数据安全性为 重中之重。这在动机中有所介绍。 仲裁队列应被视为复制队列类型的默认选项。
 * 与传统镜像队列相比，仲裁队列在行为上也存在重要差异和一些限制， 包括特定于工作负载的，例如，当使用者重复对同一消息重新排队时。
 * 某些功能（如有害消息处理）是特定的 到仲裁队列。
 * 对于可以从复制和可重复读取中受益的情况，流可能会 是比仲裁队列更好的选择。
 * <p>
 * =================================================================
 * <p>
 * 赋予动机
 * 仲裁队列旨在更安全，并提供更简单、定义明确的故障处理语义 用户在设计和操作系统时应该更容易推理。
 * 这些设计选择都有限制。为了达到此目标，仲裁队列采用不同的复制 和共识协议，并放弃对某些“瞬态”性质特征的支持。 本指南稍后将介绍这些约束和限制。
 * 仲裁队列通过原始 Jepsen 测试的重构和要求更高的版本。 这可确保它们在网络分区和故障情况下按预期运行。 新测试持续运行以发现可能的回归，并定期增强以测试新功能（例如死信）。
 * <p>
 * =================================================================
 * <p>
 * 非持久队列
 * 常规队列可能不持久。仲裁队列始终是持久的，根据其 假设的用例。
 * <p>
 * 排他性
 * 独占队列与其声明连接的生命周期相关联。 仲裁队列在设计上是可复制且持久的，因此独占属性使 在他们的上下文中没有任何意义。因此，仲裁队列不能是独占的。
 * 仲裁队列不应用作临时队列。
 * <p>
 * TTL（从 RabbitMQ 3.10 开始）
 * 仲裁队列同时支持队列 TTL 和消息 TTL （包括队列中的每队列消息 TTL 和发布者中的每消息 TTL）。 使用任何形式的消息 TTL 时，每条消息的内存开销会增加 2 个字节。
 * <p>
 * 长度限制
 * 仲裁队列支持队列长度限制。
 * 支持下 drop-head 和 reject-publish 溢出行为，但它们 不支持 reject-publish-dlx 配置，因为仲裁队列采用不同的 实现方法比经典队列。
 * 当仲裁队列达到 max-length 限制并配置 reject-publish 时 它通知每个发布渠道，从那里开始，谁将拒绝所有邮件返回 客户端。这意味着仲裁队列可能会超出其限制一些小数目 的消息，因为在通知频道时可能有消息在传输中。 队列接受的其他消息数将因 关于当时有多少条消息在传输。
 * <p>
 * =================================================================
 * <p>
 * 使用案例
 * 仲裁队列是专门构建的。它们并非设计用于解决所有问题。 它们的预期用途是用于队列长期存在并且对某些队列至关重要的拓扑 因此，系统操作的各个方面，容错和数据安全比例如， 尽可能低的延迟和高级队列功能。
 * 例如，销售系统中的传入订单或 选举制度，其中可能丢失的信息将具有显着意义 对系统正确性和功能的影响。
 * 股票行情和即时通讯系统从中受益较少或根本没有 仲裁队列。
 * 发布商应使用发布商确认，因为这是客户端可以与之交互的方式 仲裁队列共识系统。发布者确认只会发布一次 已发布的消息已成功复制到节点仲裁，并被视为“安全” 在系统的上下文中。
 * 使用者应使用手动确认来确保消息不是 成功处理将返回到队列，以便 另一个使用者可以重新尝试处理。
 * <p>
 * 何时不使用仲裁队列
 * 在某些情况下，不应使用仲裁队列。它们通常涉及：
 * 队列的临时性：暂时性或独占队列、高队列变动率（声明和删除率）
 * 尽可能低的延迟：由于其数据安全功能，底层共识算法固有的延迟更高
 * 当数据安全不是优先事项时（例如，应用程序不使用手动确认，不使用发布者确认）
 * 很长的队列积压（流可能更适合）
 * <p>
 * =================================================================
 * <p>
 * 性能对比
 * https://blog.rabbitmq.com/posts/2022/05/rabbitmq-3.10-performance-improvements/
 */
public class QuorumQueues {

    /**
     * 声明
     * 若要声明仲裁队列，请将 x-queue-type 队列参数设置为 quorum （默认值为 classic）。此参数必须由客户端提供 在队列声明时;它不能使用策略设置或更改。 这是因为策略定义或适用的策略可以动态更改，但 队列类型不能。必须在声明时指定。
     * 声明将 x-queue-type 参数设置为仲裁的队列将声明 quorum 队列 最多五个副本（默认复制因子），每个群集节点一个副本。
     * 例如，包含三个节点的群集将具有三个副本，每个节点上一个副本。 在包含七个节点的群集中，五个节点将每个节点有一个副本，但两个节点不会托管任何副本。
     * 声明后，仲裁队列可以绑定到任何交换，就像任何其他交换一样 兔子MQ队列。
     * 如果使用管理 UI 声明，则必须使用 队列类型下拉菜单。
     */
    @Test
    @SneakyThrows
    public void declaring() {

        String QUEUE_NAME = "quorum_queue";

        ConnectionFactory cf = ConnectionUtil.factory();
        try (Connection connection = cf.newConnection()) {
            Channel channel = connection.createChannel();

            channel.queueDelete(QUEUE_NAME);
            channel.queueDeclare(QUEUE_NAME, true, false, false, Collections.singletonMap("x-queue-type", "quorum"));

        }

    }

}
